﻿using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Data.SqlClient;
using System.Collections.Generic;
using System.Diagnostics;
using DynORM.Interfaces.BLLMapper.Enum;
using DynORM.BLLMapper;
using DynORM.Enums;
using DynORM.Interfaces;

namespace UnitTestProject
{
    [TestClass]
    public class BLLAdaptiveMapperUnitTests
    {
        #region db creation and clean up
        //these two members are used only in CreateDB and DropDB methods
        //production code and test methods take this setting from the config file
        const string connectionStringToServer = @"Data Source=.;Integrated Security=True;Pooling=false";
        static string connectionStringToDatabase = "Data Source=.;Initial Catalog=" + dbName + ";Integrated Security=True;Pooling=false";
        const string dbName = "TestDB";
        const DBMSTypes dbType = DBMSTypes.MSSQL;

        public string ChangeDBInConnectionString(string connectionString, string databaseName)
        {
            SqlConnectionStringBuilder csb = new SqlConnectionStringBuilder(connectionString);
            csb.InitialCatalog = databaseName;
            return csb.ConnectionString;
        }

        public bool TryConnect(string connectionString)
        {
            bool result = false;

            SqlConnection dbmsCon = new SqlConnection(connectionString);
            try
            {
                dbmsCon.Open();
                result = true;
            }
            catch
            {
                result = false;
            }
            finally
            {
                if (dbmsCon != null) dbmsCon.Dispose();
            }
            return result;
        }

        public void CreateDB(string connectionString, string dbName)
        {
            string connectionStringToDataBase = ChangeDBInConnectionString(connectionString, dbName);
            string connectionStringToServer = ChangeDBInConnectionString(connectionString, "master");
            if (TryConnect(connectionStringToDataBase) == false)
                using (SqlConnection con = new SqlConnection(connectionStringToServer))
                {
                    con.Open();
                    SqlCommand cmdCheck = new SqlCommand(String.Format(@"CREATE DATABASE [{0}]", dbName), con);
                    cmdCheck.ExecuteScalar();
                }
            //it takes some time server to create database. do not return method until db has created. 
            //otherwise if some method followed this one will try to connect to db exception occure 
            while (TryConnect(connectionStringToDataBase) == false)
                System.Threading.Thread.Sleep(10);
        }

        public void DropDB(string connectionString, string dbName)
        {
            string connectionStringToServer = ChangeDBInConnectionString(connectionString, "master");
            using (SqlConnection con = new SqlConnection(connectionStringToServer))
            {
                con.Open();
                SqlCommand cmdCheck = new SqlCommand(String.Format(@"DROP DATABASE [{0}]", dbName), con);
                cmdCheck.ExecuteScalar();
            }
        }

        [TestInitialize]
        public void TestInit()
        {

            CreateDB(connectionStringToServer, dbName);
        }

        [TestCleanup]
        public void TestClean()
        {
            DropDB(connectionStringToServer, dbName);
        }

        #endregion


        [TableAttribute(TableName = "Parent")]
        public class ParentTestBLL : BLLSuperType
        {
            [PrimaryKeyAttribute("RowID", 0)]
            public Guid ID { get; set; }

            [FieldAttribute(FieldName = "SomeField1", Attributes=new object[] { 12, 5 })]
            public decimal SomeProperty1 { get; set; }

            [FieldAttribute]
            public string SomeProperty2 { get; set; }

            private List<ChildTestBLL> relatedBLLObjs = new List<ChildTestBLL>();
            [DebuggerBrowsable(DebuggerBrowsableState.Never)]
            public List<ChildTestBLL> RelatedBLLObjs
            {
                get
                {
                    Load(new BLLAdaptiveMapper(connectionStringToDatabase, dbType));
                    return this.relatedBLLObjs;
                }
                set
                {
                    Load(new BLLAdaptiveMapper(connectionStringToDatabase, dbType));
                    this.relatedBLLObjs = value;
                }
            }
        }

        [TableAttribute(TableName = "Child")]
        [ForeignKeyDeclarationAttribute(typeof(ParentTestBLL), RelationQuantity.OneOrZeroToManyOrZero, "RelatedBLLObjs", 0)]
        public class ChildTestBLL : BLLSuperType
        {
            [PrimaryKeyAttribute("RowID", 0)]
            public Guid ID { get; set; }

            [FieldAttribute(FieldName = "SomeField3")]
            public int SomeProperty3 { get; set; }

            [FieldAttribute]
            public string SomeProperty4 { get; set; }

        }


        [TestMethod]
        public void SaveBLLObjTestParentChildManyToOneFKWontCreated()
        {
            //if referenced db table is not created DynORM won't create FK constraint and create it as soon as parent table will be created
            BLLAdaptiveMapper mapper = new BLLAdaptiveMapper(connectionStringToDatabase, dbType);
            Guid id = Guid.NewGuid();
            ChildTestBLL bllObj = (ChildTestBLL)mapper.Create(typeof(ChildTestBLL), id);
            bllObj.SomeProperty3 = 5;
            bllObj.SomeProperty4 = "9";
            bllObj.MarkAsLoaded();

            mapper.Write(bllObj, true);

            Guid parentid = Guid.NewGuid();
            ParentTestBLL parentbllObj = (ParentTestBLL)mapper.Create(typeof(ParentTestBLL), parentid);
            parentbllObj.SomeProperty1 = 15;
            parentbllObj.SomeProperty2 = "19";
            parentbllObj.MarkAsLoaded();
            parentbllObj.RelatedBLLObjs.Add(bllObj);

            mapper.Write(parentbllObj, true);

            mapper.DeleteCascade(parentbllObj);
            //mapper.Delete(bllObj);
            //object rawParentbllObj = parentbllObj;
            //mapper.ReRead(ref rawParentbllObj);
            //mapper.Delete(rawParentbllObj);
        }

        [TestMethod]
        public void SaveBLLObjTestParentChildManyToOneFKCreated()
        {
            BLLAdaptiveMapper mapper = new BLLAdaptiveMapper(connectionStringToDatabase, dbType);
            Guid id = Guid.NewGuid();
            ChildTestBLL bllObj = (ChildTestBLL)mapper.Create(typeof(ChildTestBLL), id);
            bllObj.SomeProperty3 = 5;
            bllObj.SomeProperty4 = "9";
            bllObj.MarkAsLoaded();

            Guid parentid = Guid.NewGuid();
            ParentTestBLL parentbllObj = (ParentTestBLL)mapper.Create(typeof(ParentTestBLL), parentid);
            parentbllObj.SomeProperty1 = 15;
            parentbllObj.SomeProperty2 = "19";
            parentbllObj.MarkAsLoaded();
            parentbllObj.RelatedBLLObjs.Add(bllObj);

            mapper.Write(parentbllObj, true);

            //mapper.Write(bllObj, true);

            mapper.DeleteCascade(parentbllObj);
            //mapper.Delete(bllObj);
            //object rawParentbllObj = parentbllObj;
            //mapper.ReRead(ref rawParentbllObj);
            //mapper.Delete(rawParentbllObj);
        }

        [TestMethod]
        public void SaveBatchBLLObjTestParentChildManyToOneFKCreated()
        {
            //if referenced db table is not created DynORM won't create FK constraint and create it as soon as parent table will be created

            BLLAdaptiveMapper mapper = new BLLAdaptiveMapper(connectionStringToDatabase, dbType);
            Guid id = Guid.NewGuid();
            ChildTestBLL bllObj = (ChildTestBLL)mapper.Create(typeof(ChildTestBLL), id);
            bllObj.SomeProperty3 = 5;
            bllObj.SomeProperty4 = "9";
            bllObj.MarkAsLoaded();

            Guid parentid = Guid.NewGuid();
            ParentTestBLL parentbllObj = (ParentTestBLL)mapper.Create(typeof(ParentTestBLL), parentid);
            parentbllObj.SomeProperty1 = 15;
            parentbllObj.SomeProperty2 = "19";
            parentbllObj.MarkAsLoaded();
            parentbllObj.RelatedBLLObjs.Add(bllObj);

            mapper.WriteBatch(new object[] { parentbllObj, bllObj }, true);

            mapper.DeleteCascade(parentbllObj);
        }

        [TestMethod]
        public void ReadBLLObjTestParentChildManyToOne()
        {
            BLLAdaptiveMapper mapper = new BLLAdaptiveMapper(connectionStringToDatabase, dbType);
            Guid id = Guid.NewGuid();
            ChildTestBLL bllObj = (ChildTestBLL)mapper.Create(typeof(ChildTestBLL), id);
            bllObj.SomeProperty3 = 5;
            bllObj.SomeProperty4 = "9";
            bllObj.MarkAsLoaded();

            Guid parentid = Guid.NewGuid();
            ParentTestBLL parentbllObj = (ParentTestBLL)mapper.Create(typeof(ParentTestBLL), parentid);
            parentbllObj.SomeProperty1 = 15;
            parentbllObj.SomeProperty2 = "19";
            parentbllObj.MarkAsLoaded();
            parentbllObj.RelatedBLLObjs.Add(bllObj);

            mapper.WriteBatch(new object[] { parentbllObj, bllObj }, true);

            ParentTestBLL retrievedParent = (ParentTestBLL)mapper.Read(typeof(ParentTestBLL), parentid);
            Assert.AreEqual<decimal>(parentbllObj.SomeProperty1, retrievedParent.SomeProperty1);
            Assert.AreEqual<string>(parentbllObj.SomeProperty2, retrievedParent.SomeProperty2);
            Assert.AreEqual<int>(parentbllObj.RelatedBLLObjs[0].SomeProperty3, retrievedParent.RelatedBLLObjs[0].SomeProperty3);
            Assert.AreEqual<string>(parentbllObj.RelatedBLLObjs[0].SomeProperty4, retrievedParent.RelatedBLLObjs[0].SomeProperty4);

            mapper.DeleteCascade(parentbllObj);
        }

        [TestMethod]
        public void DeleteChildBLLObjTestParentChildManyToOne()
        {
            IBLLAdaptiveMapper mapper = new BLLAdaptiveMapper(connectionStringToDatabase, dbType);
            Guid id = Guid.NewGuid();
            ChildTestBLL bllObj = (ChildTestBLL)mapper.Create(typeof(ChildTestBLL), id);
            bllObj.SomeProperty3 = 5;
            bllObj.SomeProperty4 = "9";
            bllObj.MarkAsLoaded();

            Guid parentid = Guid.NewGuid();
            ParentTestBLL parentbllObj = (ParentTestBLL)mapper.Create(typeof(ParentTestBLL), parentid);
            parentbllObj.SomeProperty1 = 15;
            parentbllObj.SomeProperty2 = "19";
            parentbllObj.MarkAsLoaded();
            parentbllObj.RelatedBLLObjs.Add(bllObj);

            mapper.WriteBatch(new object[] { parentbllObj, bllObj }, true);

            mapper.Delete(bllObj);

            ParentTestBLL retrievedParent = (ParentTestBLL)mapper.Read(typeof(ParentTestBLL), parentid);

            Assert.AreEqual<int>(0, retrievedParent.RelatedBLLObjs.Count);
        }

        [TestMethod]
        public void DeleteBatchBLLObjTestParentChildManyToOne()
        {
            IBLLAdaptiveMapper mapper = new BLLAdaptiveMapper(connectionStringToDatabase, dbType);
            Guid id = Guid.NewGuid();
            ChildTestBLL bllObj = (ChildTestBLL)mapper.Create(typeof(ChildTestBLL), id);
            bllObj.SomeProperty3 = 5;
            bllObj.SomeProperty4 = "9";
            bllObj.MarkAsLoaded();

            Guid parentid = Guid.NewGuid();
            ParentTestBLL parentbllObj = (ParentTestBLL)mapper.Create(typeof(ParentTestBLL), parentid);
            parentbllObj.SomeProperty1 = 15;
            parentbllObj.SomeProperty2 = "19";
            parentbllObj.MarkAsLoaded();
            parentbllObj.RelatedBLLObjs.Add(bllObj);

            mapper.WriteBatch(new object[] { parentbllObj, bllObj }, true);

            mapper.DeleteBatch(new object[] { bllObj, parentbllObj });

            ParentTestBLL retrievedParent = (ParentTestBLL)mapper.Read(typeof(ParentTestBLL), parentid);
            ChildTestBLL retrievedChild = (ChildTestBLL)mapper.Read(typeof(ChildTestBLL), id);
            Assert.IsNull(retrievedParent);
            Assert.IsNull(retrievedChild);
        }

        [TestMethod]
        public void DeleteCascadeBLLObjTestParentChildManyToOne()
        {
            BLLAdaptiveMapper mapper = new BLLAdaptiveMapper(connectionStringToDatabase, dbType);
            Guid id = Guid.NewGuid();
            ChildTestBLL bllObj = (ChildTestBLL)mapper.Create(typeof(ChildTestBLL), id);
            bllObj.SomeProperty3 = 5;
            bllObj.SomeProperty4 = "9";
            bllObj.MarkAsLoaded();

            Guid parentid = Guid.NewGuid();
            ParentTestBLL parentbllObj = (ParentTestBLL)mapper.Create(typeof(ParentTestBLL), parentid);
            parentbllObj.SomeProperty1 = 15;
            parentbllObj.SomeProperty2 = "19";
            parentbllObj.MarkAsLoaded();
            parentbllObj.RelatedBLLObjs.Add(bllObj);

            mapper.WriteBatch(new object[] { parentbllObj, bllObj }, true);

            mapper.DeleteCascade(parentbllObj);

            ParentTestBLL retrievedParent = (ParentTestBLL)mapper.Read(typeof(ParentTestBLL), parentid);
            ChildTestBLL retrievedChild = (ChildTestBLL)mapper.Read(typeof(ChildTestBLL), id);
            Assert.IsNull(retrievedParent);
            Assert.IsNull(retrievedChild);
        }

        [TestMethod]
        public void DeleteParentBLLObjTestParentChildManyToOne()
        {
            IBLLAdaptiveMapper mapper = new BLLAdaptiveMapper(connectionStringToDatabase, dbType);
            Guid id = Guid.NewGuid();
            ChildTestBLL bllObj = (ChildTestBLL)mapper.Create(typeof(ChildTestBLL), id);
            bllObj.SomeProperty3 = 5;
            bllObj.SomeProperty4 = "9";
            bllObj.MarkAsLoaded();

            Guid parentid = Guid.NewGuid();
            ParentTestBLL parentbllObj = (ParentTestBLL)mapper.Create(typeof(ParentTestBLL), parentid);
            parentbllObj.SomeProperty1 = 15;
            parentbllObj.SomeProperty2 = "19";
            parentbllObj.MarkAsLoaded();
            parentbllObj.RelatedBLLObjs.Add(bllObj);

            mapper.WriteBatch(new object[] { parentbllObj, bllObj }, true);

            mapper.Delete(parentbllObj);

            ChildTestBLL retrievedChild = (ChildTestBLL)mapper.Read(typeof(ChildTestBLL), id);
            Assert.IsNotNull(retrievedChild);

            ParentTestBLL retrievedParent = (ParentTestBLL)mapper.Read(typeof(ParentTestBLL), parentid);
            Assert.IsNull(retrievedParent);
        }

        [TestMethod]
        public void ReadLazyBLLObjTestParentChildManyToOne()
        {
            BLLAdaptiveMapper mapper = new BLLAdaptiveMapper(connectionStringToDatabase, dbType);
            Guid id = Guid.NewGuid();
            ChildTestBLL bllObj = (ChildTestBLL)mapper.Create(typeof(ChildTestBLL), id);
            bllObj.SomeProperty3 = 5;
            bllObj.SomeProperty4 = "9";
            bllObj.MarkAsLoaded();

            Guid parentid = Guid.NewGuid();
            ParentTestBLL parentbllObj = (ParentTestBLL)mapper.Create(typeof(ParentTestBLL), parentid);
            parentbllObj.SomeProperty1 = 15;
            parentbllObj.SomeProperty2 = "19";
            parentbllObj.MarkAsLoaded();
            parentbllObj.RelatedBLLObjs.Add(bllObj);

            mapper.WriteBatch(new object[] { parentbllObj, bllObj }, true);

            ParentTestBLL retrievedParent = (ParentTestBLL)mapper.ReadLazy(typeof(ParentTestBLL), parentid);
            //BLLAdaptiveMapper activate it with reflection with parameterless ctor
            //it's important init Mapper property afterward if lazy loading is using!!!!!
            //retrievedParent.Mapper = mapper; this line commented out just because we init Mapper in property getter and setter
            Assert.AreEqual<decimal>(parentbllObj.SomeProperty1, retrievedParent.SomeProperty1);
            Assert.AreEqual<string>(parentbllObj.SomeProperty2, retrievedParent.SomeProperty2);
            Assert.AreEqual<int>(parentbllObj.RelatedBLLObjs[0].SomeProperty3, retrievedParent.RelatedBLLObjs[0].SomeProperty3);
            Assert.AreEqual<string>(parentbllObj.RelatedBLLObjs[0].SomeProperty4, retrievedParent.RelatedBLLObjs[0].SomeProperty4);

            mapper.DeleteCascade(parentbllObj);
        }
    }
}
